﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.OpenSsl;
using Org.BouncyCastle.X509;
using Org.BouncyCastle.Crypto.Prng;
using Org.BouncyCastle.Asn1;
using Org.BouncyCastle.Asn1.X509;
using Org.BouncyCastle.Asn1.Sec;

namespace kyanhaHarness
{
    /// <summary>
    /// Holds the key and its associated certificates
    /// </summary>
    public class KeyCertHolder
    {
        #region Static members
        // Cryptographic Entropy Source
        static private SecureRandom _sr;
        // RandomGenerator to hold the CryptoApi linkage
        static private IRandomGenerator _irg;
        static private X509V3CertificateGenerator _cgen;

        static readonly string certfileprefix = "cert";
        static readonly string keyfileprefix = "key";
        static readonly string postfile = ".pem";
        #endregion

        #region Instance members

        readonly string arraylocation;

        private AsymmetricCipherKeyPair _kp;
        private X509Certificate _cert = null ;
        private List<X509Certificate> _cred;
        private int x = 0; // Where in the array are we?
        private int y = 0; // ...useful for filename
     /* private IAsymmetricKeyPairGenerator _kpgen;       defined at CreateCred */

        #endregion

        public AsymmetricCipherKeyPair Key
        {
            get
            {
                return new AsymmetricCipherKeyPair(_kp.Public,_kp.Private);
            }
        }

        public void AddCred(X509Certificate cred)
        {
            _cred.Add(cred);
        }

        #region Static Constructor
        
        static KeyCertHolder( )
        {
            _irg = new CryptoApiRandomGenerator();
            _sr = new SecureRandom(_irg);
            _cgen = new X509V3CertificateGenerator();
        }
        #endregion

#region Instance Constructor Wrappers
        public KeyCertHolder()
            : this(0, 0) { }
#endregion

        public KeyCertHolder(int x, int y)
        {

            // First, set up the object
            _cred = new List<X509Certificate>();

            this.x = x;
            this.y = y;

            arraylocation = "[" + x.ToString("x2") + "," + y.ToString("x2") + "]";

            Console.Write(arraylocation);
            // We want to ensure the key and at least one cred is set.
            // First, try to load what might already be there.

            try
            {
                Deserialize();
            }
            catch (FileNotFoundException)
            {
                // It's not there
                CreateCred();
            }

            catch (ApplicationException e)
            {
                // It's not what we expect it to be
                Console.WriteLine("Huh, this is odd.  I just got a " + e.Message + ", and I don't know what to do.");
                Console.WriteLine(@"Instead, I'm going to stop my current run and wait for you to press a key so you can see it.");
                Console.ReadKey();
            }
            // set the array's base element to what's read or created (or uassigned)
            _cred[0] = _cert;
        }


        // Defined here to make it easier to see what kind of cipher has been selected
        // so that the strength selection makes sense -- not all bit strengths are powers
        // of 2.  It's actually 521.  Other strengths are 384, 256, 239, 224, and 192.
        private IAsymmetricCipherKeyPairGenerator _kpgen = new ECKeyPairGenerator("EC");

        private void CreateCred( )
        {
            _kpgen.Init(new KeyGenerationParameters(
                                        _sr, 521));
            Console.Write(":generating");
            this._kp = _kpgen.GenerateKeyPair();
            Console.Write(":generated");
            _cgen.Reset();
            _cgen.SetNotBefore(DateTime.Now);
            _cgen.SetIssuerDN(new X509Name("OU=\"" + arraylocation + "\""));
            _cgen.SetNotAfter(new DateTime(2999, 12, 31, 23, 59, 59, DateTimeKind.Utc));
            _cgen.SetSubjectDN(new
                Org.BouncyCastle.Asn1.X509.X509Name("OU=\""+arraylocation+"\""));
            _cgen.SetPublicKey(_kp.Public);
            _cgen.SetSignatureAlgorithm(
                Org.BouncyCastle.Asn1.X9.X9ObjectIdentifiers.ECDsaWithSha256.ToString());
            _cgen.SetSerialNumber(
                                new Org.BouncyCastle.Math.BigInteger(
                                    8 * 8 - 1,  // number of bits to generate
                                                // set this way to avoid a 9-byte encoding
                                    _sr));      // source to generate from

            _cert = _cgen.Generate(_kp.Private);
            Console.Write(":certified");
            try
            {
                _cert.Verify(_kp.Public);
            }
            catch (Exception e)
            {
                Console.Write("this is a problem: " + e.ToString());
            }
            Console.Write(":verified");

            Serialize();
        }

        public void Serialize()
        {
            var pkcs8gen = new Pkcs8Generator(_kp.Private);
            var filename = keyfileprefix + arraylocation + postfile;
            // write it all out
            using (System.IO.TextWriter sw =
                            new System.IO.StreamWriter(
                                        filename,
                                        false,
                                        new ASCIIEncoding(),
                                        2048))
            {
                Org.BouncyCastle.OpenSsl.PemWriter mywriter =
                      new Org.BouncyCastle.OpenSsl.PemWriter(sw);
                // mywriter.WriteObject(ECencoded);
                mywriter.WriteObject(pkcs8gen.Generate());
            }
            filename = certfileprefix + arraylocation + postfile;
            using (System.IO.TextWriter sw =
                new System.IO.StreamWriter(
                    filename,
                    false,
                    new ASCIIEncoding(),
                    2048))
            {
                PemWriter mywriter = new PemWriter(sw);
                for (int i = 0; i < _cred.Count; i++)
                {
                    mywriter.WriteObject(_cert);
                }
                sw.Flush();
            }
            Console.Write(":serialized");
        }

        public void Deserialize()
        {
            string keyfilename = keyfileprefix + arraylocation + postfile;
            string certfilename = certfileprefix + arraylocation + postfile;
            var filename = keyfilename;
            try
            {
                Console.Write(":deserializing");
                using (TextReader ksr = new StreamReader(
                keyfilename, new ASCIIEncoding(), false, 2048))
                {
                    using (TextReader csr = new StreamReader(
                        certfilename, new ASCIIEncoding(), false, 2048))
                    {

                        PemReader kpr = new PemReader(ksr);
                        PemReader cpr = new PemReader(csr);

                        //System.Diagnostics.Debugger.Break();

                      //  _kp.Private = kpr.ReadObject() as AsymmetricKeyParameter;
                        ECPrivateKeyParameters thisprivate = kpr.ReadObject() as ECPrivateKeyParameters;
                        if (null == thisprivate)
                            throw new ApplicationException("failed to read private key");
                        Console.Write(":read.keypair");


                        _cert = cpr.ReadObject() as X509Certificate;
                        if (null == _cert)
                            throw new ApplicationException("failed to read self-certificate");
                        Console.Write(":read.selfcert");
                        AsymmetricKeyParameter mei = _cert.GetPublicKey();
                        _kp = new AsymmetricCipherKeyPair(mei, thisprivate);
                        if (null == _kp)
                            throw new Exception("keypair is still null " + arraylocation);

                        _cred[0] = _cert;
                    }
                }
            }
            catch (ApplicationException)
            {
                Console.Write(":regeneration");
                CreateCred();
            }
        }

    }
}
